package rock.common.util.concurrent.threadpool;

import com.google.common.util.concurrent.ThreadFactoryBuilder;
import lombok.extern.slf4j.Slf4j;

import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * 创建线程池工具类
 */
@Slf4j
public final class ThreadPoolFactoryUtil {

    private static final Map<String, ExecutorService> THREAD_POOLS = new ConcurrentHashMap<>();

    public ThreadPoolFactoryUtil() {
    }

    public static ExecutorService createCustomThreadPoolIfAbsent(String threadNameGroup) {
        ThreadPoolCustomConfig threadPoolCustomConfig = new ThreadPoolCustomConfig();
        return createCustomThreadPoolIfAbsent(threadPoolCustomConfig, threadNameGroup, false);
    }

    public static ExecutorService createCustomThreadPoolIfAbsent(String threadNameGroup, ThreadPoolCustomConfig threadPoolCustomConfig) {
        return createCustomThreadPoolIfAbsent(threadPoolCustomConfig, threadNameGroup, false);
    }

    public static ExecutorService createCustomThreadPoolIfAbsent(ThreadPoolCustomConfig threadPoolCustomConfig, String threadNameGroup, Boolean daemon) {
        ExecutorService threadPool
                = THREAD_POOLS.computeIfAbsent(threadNameGroup,
                k -> createThreadPool(threadPoolCustomConfig, threadNameGroup, daemon)
        );
        if (threadPool.isShutdown() || threadPool.isTerminated()) {
            THREAD_POOLS.remove(threadNameGroup);
            threadPool = createThreadPool(threadPoolCustomConfig, threadNameGroup, daemon);
            THREAD_POOLS.put(threadNameGroup, threadPool);
        }

        return threadPool;
    }

    private static ExecutorService createThreadPool(ThreadPoolCustomConfig threadPoolCustomConfig, String threadNameGroup, Boolean daemon) {
        ThreadFactory threadFactory = createThreadFactory(threadNameGroup, daemon);
        return new ThreadPoolExecutor(threadPoolCustomConfig.getCorePoolSize(), threadPoolCustomConfig.getMaximumPoolSize(),
                threadPoolCustomConfig.getKeepAliveTime(), threadPoolCustomConfig.getUnit(), createQueue(threadPoolCustomConfig.QUEUE_CAPACITY), threadFactory);
    }

    private static BlockingQueue<Runnable> createQueue(int capacity) {
        if (capacity > 0) {
            return new LinkedBlockingDeque<>(capacity);
        } else {
            return new SynchronousQueue<>();
        }
    }

    public static ThreadFactory createThreadFactory(String threadNameGroup, Boolean daemon) {
        if (threadNameGroup != null) {
            if (daemon != null) {
                return new ThreadFactoryBuilder()
                        .setNameFormat(threadNameGroup + "-%d")
                        .setDaemon(daemon)
                        .build();
            } else {
                return new ThreadFactoryBuilder().setNameFormat(threadNameGroup + "-%d").build();
            }
        }
        return Executors.defaultThreadFactory();
    }

    public static void printThreadPoolStatus(ThreadPoolExecutor threadPool) {
        ScheduledExecutorService scheduledExecutorService
                = new ScheduledThreadPoolExecutor(1, createThreadFactory("print-thread-pool-status", false));

        scheduledExecutorService.scheduleAtFixedRate(() -> {
            log.info("============ThreadPool Status=============");
            log.info("ThreadPool size:[{}]", threadPool.getCorePoolSize());
            log.info("Active Threads: [{}]", threadPool.getActiveCount());
            log.info("Number of Tasks : [{}]", threadPool.getCompletedTaskCount());
            log.info("Number of Tasks in Queue: {}", threadPool.getQueue().size());
            log.info("===========================================");
        }, 0, 1, TimeUnit.SECONDS);
    }

    public static void shutDownAllThreadPool() {
        log.info("call shutdown all");
        THREAD_POOLS.entrySet().parallelStream().forEach(entry -> {
            ExecutorService executorService = entry.getValue();
            executorService.shutdown();
            log.info("shutdown thread:[{}], [{}]", entry.getKey(), executorService.isTerminated());
            try {
                executorService.awaitTermination(10, TimeUnit.SECONDS);
            } catch (InterruptedException e) {
                log.error("shutdown interrupt");
                executorService.shutdownNow();
            }
        });
    }

}
